#include "praxis/services/trigger_engine.h"

#include <chrono>
#include <fstream>
#include <iomanip>
#include <map>
#include <string>
#include <vector>
#include <sstream>

namespace praxis {
struct TriggerRule {
  std::string name;
  std::string description;
  std::string type;
  std::string variable_to_watch;
  std::string op;
  double value;
};

struct CTriggerEngine::Impl {
  std::vector<TriggerRule> rules_;
  std::map<std::string, bool>
      trigger_states_;  // Tracks current state (true=triggered)
  long long log_id_counter_ = 0;
  const std::string log_filename_ = "trigger_log.csv";

  explicit Impl(const json& defs) {
    if (!defs.is_array()) return;
    for (const auto& rule_def : defs) {
      TriggerRule r;
      r.name = rule_def.value("name", "");
      r.description = rule_def.value("description", "");
      r.type = rule_def.value("type", "Generic");
      r.variable_to_watch = rule_def.value("variable_to_watch", "");
      if (rule_def.contains("condition")) {
        r.op = rule_def["condition"].value("operator", "");
        r.value = rule_def["condition"].value("value", 0.0);
      }
      if (!r.name.empty() && !r.variable_to_watch.empty() && !r.op.empty()) {
        rules_.push_back(r);
        trigger_states_[r.name] = false;  // Initialize all as 'not triggered'
      }
    }
    InitializeLogFile();
  }

  void Evaluate(const json& state);
  void LogTriggerEvent(const TriggerRule& rule, bool is_triggered);
  void InitializeLogFile();
  std::string GetCurrentDateTimeString();
};

CTriggerEngine::CTriggerEngine(const json& trigger_definitions)
    : impl_(std::make_unique<Impl>(trigger_definitions)) {}

CTriggerEngine::~CTriggerEngine() = default;

void CTriggerEngine::Evaluate(const json& current_machine_state) {
  impl_->Evaluate(current_machine_state);
}

void CTriggerEngine::Impl::Evaluate(const json& state) {
  for (const auto& rule : rules_) {
    if (!state.contains(rule.variable_to_watch)) continue;
    const auto& val_json = state[rule.variable_to_watch];
    if (!val_json.is_number()) continue;

    double live_value = val_json.get<double>();
    bool condition_met = false;

    if (rule.op == "greater_than")
      condition_met = live_value > rule.value;
    else if (rule.op == "less_than")
      condition_met = live_value < rule.value;
    else if (rule.op == "equal_to")
      condition_met = std::abs(live_value - rule.value) < 1e-6;

    bool previous_state = trigger_states_.at(rule.name);

    if (condition_met && !previous_state) {
      trigger_states_[rule.name] = true;
      LogTriggerEvent(rule, true);  // Log "triggered"
    } else if (!condition_met && previous_state) {
      trigger_states_[rule.name] = false;
      LogTriggerEvent(rule, false);  // Log "released"
    }
  }
}

void CTriggerEngine::Impl::LogTriggerEvent(const TriggerRule& rule,
                                           bool is_triggered) {
  std::ofstream log_file(log_filename_, std::ios_base::app);
  if (!log_file.is_open()) return;

  log_id_counter_++;
  std::string status = is_triggered ? "triggered" : "released";
  log_file << log_id_counter_ << "," << GetCurrentDateTimeString() << ","
           << rule.name << "," << status << "," << rule.type << ","
           << "\"" << rule.description
           << "\"";  // Enclose description in quotes
}

void CTriggerEngine::Impl::InitializeLogFile() {
  // Check if file exists. If not, write header.
  std::ifstream f(log_filename_);
  if (!f.good()) {
    std::ofstream log_file(log_filename_);
    if (log_file.is_open()) {
      log_file << "id,datetime,name,status,type,desc";
    }
  }
}

std::string CTriggerEngine::Impl::GetCurrentDateTimeString() {
  auto now = std::chrono::system_clock::now();
  auto in_time_t = std::chrono::system_clock::to_time_t(now);
  std::stringstream ss;
  tm local_tm;
  localtime_s(&local_tm, &in_time_t);  // Use thread-safe version
  ss << std::put_time(&local_tm, "%Y-%m-%d %X");
  return ss.str();
}
}  // namespace praxis